﻿#include "precomp.h"
#include "jsiftareaeditor.h"
#include "qwt_plot.h"
#include "qwt_plot_shapeitem.h"
#include "qwt_plot_canvas.h"
#include "qwt_widget_overlay.h"

// - class JWidgetOverlay -

class JWidgetOverlay: public QwtWidgetOverlay
{
public:
    JWidgetOverlay(QWidget *parent, JSiftAreaEditor *editor):
        QwtWidgetOverlay(parent),
        d_editor(editor)
    {
        setMaskMode(QwtWidgetOverlay::AlphaMask);
        setRenderMode(QwtWidgetOverlay::AutoRenderMode);
    }

protected:
    virtual void drawOverlay(QPainter *painter) const
    {
        d_editor->drawOverlay(painter);
    }

    virtual QRegion maskHint() const
    {
        return d_editor->maskHint();
    }

private:
    JSiftAreaEditor *d_editor;
};

// - class JSiftAreaEditor -

JSiftAreaEditor::JSiftAreaEditor(QwtPlot *plot) :
    QObject(plot),
    d_isEnabled(false),
    d_overlay(0),
    d_isMoved(false),
    d_isResized(false),
    d_mousePressed(false),
    d_regionPressed(RegionInvalid),
    d_editedItem(0)
{
    setEnabled(true);

    //
    addShape(QPainterPath(), QStringLiteral("sift area"));
    d_canvasCursor = plot->canvas()->cursor();
}

JSiftAreaEditor::~JSiftAreaEditor()
{
    delete d_overlay;
}

QwtPlot *JSiftAreaEditor::plot()
{
    return qobject_cast<QwtPlot *>(parent());
}

const QwtPlot *JSiftAreaEditor::plot() const
{
    return qobject_cast<const QwtPlot *>(parent());
}

void JSiftAreaEditor::clearShape()
{
    // 保留第一个作为默认shapeItem
    while (siftShapes.count() > 1) {
        QwtPlotShapeItem *shapeItem = siftShapes.last();
        if (shapeItem == 0) {
            continue;
        }
        shapeItem->detach();
        delete shapeItem;
        siftShapes.removeLast();
    }

    //
    if (!siftShapes.isEmpty()) {
        siftShapes.first()->setShape(QPainterPath());
    }
}

void JSiftAreaEditor::addShape(const QPainterPath &path, const QString &title, bool notify)
{
    //
    if (siftShapes.count() == 1) {
        QwtPlotShapeItem *shapeItem = siftShapes.first();
        if (path != shapeItem->shape()) {
            shapeItem->setTitle(title);
            shapeItem->setShape(path);
            if (notify) {
                Q_EMIT siftAreaChanged(0, path.boundingRect());
            }
        }
        return;
    }

    //
    QwtPlotShapeItem *shapeItem = createShapeItem(path, title);
    if (shapeItem == 0) {
        return;
    }

    //
    shapeItem->attach(plot());

    //
    siftShapes.append(shapeItem);
}

void JSiftAreaEditor::setShape(int index, const QPainterPath &path, bool notify)
{
    //
    QwtPlotShapeItem *shapeItem = shapeAt(index);
    if (shapeItem == 0) {
        return;
    }

    //
    shapeItem->setShape(path);

    //
    if (notify) {
        Q_EMIT siftAreaChanged(index, shapeItem->boundingRect());
    }
}

QwtPlotShapeItem *JSiftAreaEditor::shapeAt(int index) const
{
    if (index < 0 || index >= siftShapes.count()) {
        return 0;
    }

    return siftShapes.at(index);
}

int JSiftAreaEditor::shapeCount() const
{
    return siftShapes.count();
}

bool JSiftAreaEditor::eventFilter(QObject *object, QEvent *event)
{
    QwtPlot *plot = this->plot();
    if (plot && object == plot->canvas()) {
        switch (event->type()) {
        case QEvent::MouseButtonPress:
        {
            widgetMousePress(dynamic_cast<QMouseEvent *>(event));
            break;
        }
        case QEvent::MouseMove:
        {
            widgetMouseMove(dynamic_cast<QMouseEvent *>(event));
            break;
        }
        case QEvent::MouseButtonRelease:
        {
            widgetMouseRelease(dynamic_cast<QMouseEvent *>(event));
            break;
        }
        default:
            break;
        }
    }

    return false;
}

void JSiftAreaEditor::drawOverlay(QPainter *painter) const
{
    const QwtPlot *plot = this->plot();
    if (plot == 0 || d_editedItem == 0) {
        return;
    }

    const QwtScaleMap xMap = plot->canvasMap(d_editedItem->xAxis());
    const QwtScaleMap yMap = plot->canvasMap(d_editedItem->yAxis());

    painter->setRenderHint(QPainter::Antialiasing,
                           d_editedItem->testRenderHint(QwtPlotItem::RenderAntialiased));
    d_editedItem->draw(painter, xMap, yMap, plot->canvas()->contentsRect());
}

QRegion JSiftAreaEditor::maskHint() const
{
    return maskHint(d_editedItem);
}

bool JSiftAreaEditor::isEnabled() const
{
    return d_isEnabled;
}

QList<QRectF> JSiftAreaEditor::siftAreas() const
{
    QList<QRectF> rects;
    QListIterator<QwtPlotShapeItem *> citerSiftShapes(siftShapes);
    while (citerSiftShapes.hasNext()) {
        const QwtPlotShapeItem *shapeItem = citerSiftShapes.next();
        if (shapeItem == 0) {
            continue;
        }
        rects.append(shapeItem->boundingRect());
    }

    return rects;
}

void JSiftAreaEditor::setAxisAutoScale(bool enabled)
{
    QwtPlot *plot = this->plot();
    if (plot != 0) {
        plot->setAxisAutoScale(QwtPlot::xBottom, enabled);
        plot->setAxisAutoScale(QwtPlot::yLeft, enabled);
    }
}

void JSiftAreaEditor::setEnabled(bool on)
{
    if (on == d_isEnabled) {
        return;
    }

    QwtPlot *plot = this->plot();
    if (plot != 0) {
        d_isEnabled = on;

        if (on) {
            plot->canvas()->installEventFilter(this);
        } else {
            plot->canvas()->removeEventFilter(this);

            if (d_overlay != 0) {
                delete d_overlay;
                d_overlay = 0;
            }
        }
    }
}

void JSiftAreaEditor::widgetMousePress(const QMouseEvent *event)
{
    if (d_overlay == 0 && event->button() == Qt::LeftButton) {
        const bool accepted = focusItem(event->pos(), true);
        if (accepted) {
            d_overlay = new JWidgetOverlay(plot()->canvas(), this);
            d_overlay->updateOverlay();
            d_overlay->show();
        }
    }
}

void JSiftAreaEditor::widgetMouseMove(const QMouseEvent *event)
{
    bool accepted = focusItem(event->pos(), false);
    if (accepted && (event->modifiers() & Qt::ControlModifier)) {
        accepted = moveItem(event->pos());
        if (accepted) {
            if (d_overlay != 0) {
                d_overlay->updateOverlay();
            }
            d_isMoved = true;
        }
    }
}

void JSiftAreaEditor::widgetMouseRelease(const QMouseEvent *event)
{
    if (d_editedItem == 0) {
        plot()->canvas()->unsetCursor();
    }

    if (d_overlay != 0
            && event->button() == Qt::LeftButton) {
        //
        releaseItem(event->pos());

        //
        if (d_isMoved) {
            Q_EMIT siftAreaChanged(0, d_editedItem->boundingRect());
            d_isMoved = false;
        }
    }
}

bool JSiftAreaEditor::focusItem(const QPoint &pos, bool press)
{
    if (d_mousePressed) {
        return true;
    }

    QwtPlot *plot = this->plot();
    QwtPlotShapeItem *editedItem = itemAt(pos);
    if (editedItem) {
        d_editedItem = editedItem;
        if (press) {
            d_mousePressed = true;
            d_currentPos = pos;
            setItemVisible(d_editedItem, false);
            if (d_regionPressed == RegionCenter) {
                plot->canvas()->setCursor(Qt::ClosedHandCursor);
            }
        } else {
            d_regionPressed = regionHit(pos);
            switch (d_regionPressed) {
            case RegionTopLeft:
            case RegionBottomRight:
                plot->canvas()->setCursor(Qt::SizeFDiagCursor);
                break;
            case RegionTop:
            case RegionBottom:
                plot->canvas()->setCursor(Qt::SizeVerCursor);
                break;
            case RegionTopRight:
            case RegionBottomLeft:
                plot->canvas()->setCursor(Qt::SizeBDiagCursor);
                break;
            case RegionLeft:
            case RegionRight:
                plot->canvas()->setCursor(Qt::SizeHorCursor);
                break;
            case RegionCenter:
                plot->canvas()->setCursor(Qt::OpenHandCursor);
                break;
            default:
                plot->canvas()->setCursor(d_canvasCursor);
                break;
            }
        }
        //
        return true;
    } else {
        //
        releaseItem(pos);
        d_editedItem = 0;
    }

    return false; // don't accept the position
}

bool JSiftAreaEditor::moveItem(const QPoint &pos)
{
    QwtPlot *plot = this->plot();
    if (plot == 0 || d_editedItem == 0 || !d_mousePressed
            || d_regionPressed == RegionInvalid) {
        return false;
    }

    //
    const QwtScaleMap xMap = plot->canvasMap(d_editedItem->xAxis());
    const QwtScaleMap yMap = plot->canvasMap(d_editedItem->yAxis());
    const QPointF orgPos = QwtScaleMap::invTransform(xMap, yMap, d_currentPos);
    const QPointF curPos = QwtScaleMap::invTransform(xMap, yMap, pos);
    const QPointF dp = curPos - orgPos;
    QRectF rect = d_editedItem->boundingRect();

    switch (d_regionPressed) {
    case RegionTopLeft: rect.adjust(dp.x(), 0, 0, dp.y()); break;
    case RegionTop: rect.adjust(0, 0, 0, dp.y()); break;
    case RegionTopRight: rect.adjust(0, 0, dp.x(), dp.y()); break;
    case RegionLeft: rect.adjust(dp.x(), 0, 0, 0); break;
    case RegionCenter: rect.translate(dp); break;
    case RegionRight: rect.adjust(0, 0, dp.x(), 0); break;
    case RegionBottomLeft: rect.adjust(dp.x(), dp.y(), 0, 0); break;
    case RegionBottom: rect.adjust(0, dp.y(), 0, 0); break;
    case RegionBottomRight: rect.adjust(0, dp.y(), dp.x(), 0); break;
    default: return false;
    }

    if (rect.isEmpty()) {
        return false;
    }

    QPainterPath shape;
    shape.addRect(rect);
    d_editedItem->setShape(shape);
    d_currentPos = pos;

    return true;
}

void JSiftAreaEditor::releaseItem(const QPoint &)
{
    QwtPlot *plot = this->plot();
    if (plot == 0) {
        return;
    }

    //
    if (d_editedItem) {
        d_mousePressed = false;

        //
        if (d_regionPressed == RegionCenter) {
            plot->canvas()->setCursor(Qt::OpenHandCursor);
        }

        //
        raiseItem(d_editedItem);
        setItemVisible(d_editedItem, true);

        //
        if (d_overlay != 0) {
            delete d_overlay;
            d_overlay = 0;
        }
    } else {
        plot->canvas()->setCursor(d_canvasCursor);
    }
}

QwtPlotShapeItem *JSiftAreaEditor::itemAt(const QPoint &pos) const
{
    const QwtPlot *plot = this->plot();
    if (plot == 0) {
        return 0;
    }

    // translate pos into the plot coordinates
    double coords[QwtPlot::axisCnt];
    coords[QwtPlot::xBottom] = plot->canvasMap(QwtPlot::xBottom).invTransform(pos.x());
    coords[QwtPlot::xTop] = plot->canvasMap(QwtPlot::xTop).invTransform(pos.x());
    coords[QwtPlot::yLeft] = plot->canvasMap(QwtPlot::yLeft).invTransform(pos.y());
    coords[QwtPlot::yRight] = plot->canvasMap(QwtPlot::yRight).invTransform(pos.y());

    QwtPlotItemList items = plot->itemList();
    for (int i = items.size() - 1; i >= 0; --i) {
        QwtPlotItem *item = items[i];
        if (item->isVisible() && item->rtti() == QwtPlotItem::Rtti_PlotShape) {
            QwtPlotShapeItem *shapeItem = static_cast<QwtPlotShapeItem *>(item);
            const QPointF p(coords[item->xAxis()], coords[item->yAxis()]);
            if (shapeItem->boundingRect().contains(p)
                    && shapeItem->shape().contains(p)) {
                return shapeItem;
            }
        }
    }

    return 0;
}

void JSiftAreaEditor::raiseItem(QwtPlotShapeItem *shapeItem)
{
    const QwtPlot *plot = this->plot();
    if (plot == 0 || shapeItem == 0) {
        return;
    }

    const QwtPlotItemList items = plot->itemList();
    for (int i = items.size() - 1; i >= 0; i--) {
        QwtPlotItem *item = items[ i ];
        if (shapeItem == item)
            return;

        if (item->isVisible()
                && item->rtti() == QwtPlotItem::Rtti_PlotShape) {
            shapeItem->setZ(item->z() + 1);
            return;
        }
    }
}

void JSiftAreaEditor::setItemVisible(QwtPlotShapeItem *item, bool on)
{
    QwtPlot *plot = this->plot();
    if (plot == 0 || item == 0 || item->isVisible() == on) {
        return;
    }

    const bool doAutoReplot = plot->autoReplot();
    plot->setAutoReplot(false);

    item->setVisible(on);

    plot->setAutoReplot(doAutoReplot);

    /*
      Avoid replot with a full repaint of the canvas.
      For special combinations - f.e. using the
      raster paint engine on a remote display -
      this makes a difference.
     */
    QwtPlotCanvas *canvas = qobject_cast<QwtPlotCanvas *>(plot->canvas());
    if (canvas) {
        canvas->invalidateBackingStore();
    }

    canvas->update(maskHint(item));
}

QRegion JSiftAreaEditor::maskHint(QwtPlotShapeItem *shapeItem) const
{
    const QwtPlot *plot = this->plot();
    if (plot == 0 || shapeItem == 0) {
        return QRegion();
    }

    const QwtScaleMap xMap = plot->canvasMap(shapeItem->xAxis());
    const QwtScaleMap yMap = plot->canvasMap(shapeItem->yAxis());

    QRect rect = QwtScaleMap::transform(xMap, yMap, shapeItem->boundingRect()).toRect();

    const int m = 5; // some margin for the pen
    return rect.adjusted(-m, -m, m, m);
}

QwtPlotShapeItem *JSiftAreaEditor::createShapeItem(const QPainterPath &path, const QString &title)
{
    //
    QwtPlotShapeItem *shapeItem = new QwtPlotShapeItem(title);
    shapeItem->setItemAttribute(QwtPlotItem::Legend, false);
    shapeItem->setLegendMode(QwtPlotShapeItem::LegendShape);
    shapeItem->setLegendIconSize(QSize(20, 20));
    shapeItem->setRenderHint(QwtPlotItem::RenderAntialiased, true);

    //
    QColor fillColor(0x2a, 0x3a, 0x48, 200);
    QPen pen(fillColor.lighter(300), 1);
    pen.setJoinStyle(Qt::RoundJoin);
    shapeItem->setPen(pen);
    shapeItem->setBrush(fillColor);

    //
    shapeItem->setShape(path);

    return shapeItem;
}

JSiftAreaEditor::RegionType JSiftAreaEditor::regionHit(const QPoint &pos) const
{
    RegionType regionType = RegionInvalid;

    const QwtPlot *plot = this->plot();
    if (plot == 0 || d_editedItem == 0) {
        return regionType;
    }

    const QwtScaleMap xMap = plot->canvasMap(d_editedItem->xAxis());
    const QwtScaleMap yMap = plot->canvasMap(d_editedItem->yAxis());
    const QRectF rect = QwtScaleMap::transform(xMap, yMap, d_editedItem->boundingRect());

    const int margin = 6;
    QRectF regions[RegionCount];
    regions[RegionTopLeft] = QRectF(rect.topLeft(), QSize(margin, margin));
    regions[RegionTop] = QRectF(rect.left() + margin, rect.top(),
                                rect.right() - rect.left() - margin * 2, margin);
    regions[RegionTopRight] = QRectF(rect.right() - margin, rect.top(), margin, margin);
    regions[RegionLeft] = QRectF(rect.left(), rect.top() + margin,
                                 margin, rect.bottom() - rect.top() - margin * 2);
    regions[RegionCenter] = QRect(rect.left() + margin, rect.top() + margin,
                                  rect.right() - rect.left() - margin * 2,
                                  rect.bottom() - rect.top() - margin * 2);
    regions[RegionRight] = QRectF(rect.right() - margin, rect.top() + margin,
                                  margin, rect.bottom() - rect.top() - margin * 2);
    regions[RegionBottomLeft] = QRectF(rect.left(), rect.bottom() - margin,
                                       margin, margin);
    regions[RegionBottom] = QRectF(rect.left() + margin, rect.bottom() - margin,
                                   rect.right() - rect.left() - margin * 2, margin);
    regions[RegionBottomRight] = QRectF(rect.right() - margin, rect.bottom() - margin,
                                        margin, margin);

    //
    for (int i = 0; i < RegionCount; ++i) {
        if (regions[i].contains(pos)) {
            return (RegionType)i;
        }
    }

    return regionType;
}

